﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections.ObjectModel;
using System.Reflection.Emit;
using System.Xml;
using System.Threading;

namespace Framework.Constructor
{
    public static class Reflection
    {
        public static Type[] ObtenerTipos()
        {
            Collection<Type> resultado = new Collection<Type>();
            Collection<Configuracion> configuraciones = new Collection<Configuracion>();
            Collection<String> settings = Configuracion.ObtenerConfiguraciones();

            foreach ( String setting in settings )
            {
                configuraciones.Add( new Configuracion( setting ) );
            }

            foreach ( Configuracion config in configuraciones )
            {
                foreach ( Type tipo in config.Tipos )
                {
                    resultado.Add( tipo );
                }
            }

            return resultado.ToArray();
        }


        public static Object ObtenerObjeto( String nombreAssembly, String nombreModule, XmlNode xn )
        {
            Type generetedType = ObtenerTipo( nombreAssembly, nombreModule, xn );

            // Now we have our type. Let's create an instance from it:
            object generetedObject = Activator.CreateInstance( generetedType );

            // Loop over all the generated properties, and assign the values from our XML:
            PropertyInfo[] properties = generetedType.GetProperties();

            int propertiesCounter = 0;

            // Loop over the values that we will assign to the properties
            foreach ( XmlNode node in xn.ChildNodes )
            {
                if ( node.HasChildNodes && node.ChildNodes.Count > 1 )
                {
                    Object objeto = ObtenerObjeto( nombreAssembly, nombreModule, node );
                    properties[propertiesCounter].SetValue(
                        generetedObject, objeto,
                        null );
                }
                else
                {
                    string value = node.InnerText;
                    properties[propertiesCounter].SetValue( generetedObject, value, null );
                }
                propertiesCounter++;
            }

            //Yoopy ! Return our new genereted object.
            return generetedObject;
        }

        public static Type ObtenerTipo( String nombreAssembly, String nombreModule, XmlNode xn )
        {
            // create a dynamic assembly and module 
            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = nombreAssembly;
            AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.Run );
            ModuleBuilder module = assemblyBuilder.DefineDynamicModule( nombreModule );

            // create a new type builder
            TypeBuilder typeBuilder = module.DefineType( xn.Name, TypeAttributes.Public | TypeAttributes.Class );

            // Loop over the attributes that will be used as the properties names in out new type
            foreach ( XmlNode node in xn.ChildNodes )
            {
                if ( node.HasChildNodes && node.ChildNodes.Count > 1 )
                {
                    Object propOb = ObtenerObjeto( nombreAssembly, nombreModule, node );
                    ObtenerPropiedad( typeBuilder, propOb );
                }
                else 
                    ObtenerPropiedad( typeBuilder, node );
            }

            // Generate our type
            Type generetedType = typeBuilder.CreateType();
            return generetedType;
        }

        private static void ObtenerPropiedad( TypeBuilder typeBuilder, object propOb )
        {
            //string propertyName = node.Attributes["name"].Value;
            string propertyName = propOb.GetType().FullName;

            // Generate a private field
            FieldBuilder field = typeBuilder.DefineField( "_" + propertyName, propOb.GetType(), FieldAttributes.Public );
            // Generate a public property
            PropertyBuilder property =
                typeBuilder.DefineProperty( propertyName,
                                 PropertyAttributes.None,
                                 propOb.GetType(),
                                 new Type[] { propOb.GetType() } );

            // The property set and property get methods require a special set of attributes:

            MethodAttributes GetSetAttr =
                MethodAttributes.Public |
                MethodAttributes.HideBySig;

            // Define the "get" accessor method for current private field.
            MethodBuilder currGetPropMthdBldr =
                typeBuilder.DefineMethod( "get_value",
                                           GetSetAttr,
                                           propOb.GetType(),
                                           Type.EmptyTypes );

            // Intermediate Language stuff...
            ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
            currGetIL.Emit( OpCodes.Ldarg_0 );
            currGetIL.Emit( OpCodes.Ldfld, field );
            currGetIL.Emit( OpCodes.Ret );

            // Define the "set" accessor method for current private field.
            MethodBuilder currSetPropMthdBldr =
                typeBuilder.DefineMethod( "set_value",
                                           GetSetAttr,
                                           null,
                                           new Type[] { propOb.GetType() } );

            // Again some Intermediate Language stuff...
            ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
            currSetIL.Emit( OpCodes.Ldarg_0 );
            currSetIL.Emit( OpCodes.Ldarg_1 );
            currSetIL.Emit( OpCodes.Stfld, field );
            currSetIL.Emit( OpCodes.Ret );

            // Last, we must map the two methods created above to our PropertyBuilder to 
            // their corresponding behaviors, "get" and "set" respectively. 
            property.SetGetMethod( currGetPropMthdBldr );
            property.SetSetMethod( currSetPropMthdBldr );
        }

        private static void ObtenerPropiedad( TypeBuilder typeBuilder, XmlNode node )
        {
            //string propertyName = node.Attributes["name"].Value;
            string propertyName = node.Name;

            // Generate a private field
            FieldBuilder field = typeBuilder.DefineField( "_" + propertyName, typeof( string ), FieldAttributes.Public );
            // Generate a public property
            PropertyBuilder property =
                typeBuilder.DefineProperty( propertyName,
                                 PropertyAttributes.None,
                                 typeof( string ),
                                 new Type[] { typeof( string ) } );

            // The property set and property get methods require a special set of attributes:

            MethodAttributes GetSetAttr =
                MethodAttributes.Public |
                MethodAttributes.HideBySig;

            // Define the "get" accessor method for current private field.
            MethodBuilder currGetPropMthdBldr =
                typeBuilder.DefineMethod( "get_value",
                                           GetSetAttr,
                                           typeof( string ),
                                           Type.EmptyTypes );

            // Intermediate Language stuff...
            ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
            currGetIL.Emit( OpCodes.Ldarg_0 );
            currGetIL.Emit( OpCodes.Ldfld, field );
            currGetIL.Emit( OpCodes.Ret );

            // Define the "set" accessor method for current private field.
            MethodBuilder currSetPropMthdBldr =
                typeBuilder.DefineMethod( "set_value",
                                           GetSetAttr,
                                           null,
                                           new Type[] { typeof( string ) } );

            // Again some Intermediate Language stuff...
            ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
            currSetIL.Emit( OpCodes.Ldarg_0 );
            currSetIL.Emit( OpCodes.Ldarg_1 );
            currSetIL.Emit( OpCodes.Stfld, field );
            currSetIL.Emit( OpCodes.Ret );

            // Last, we must map the two methods created above to our PropertyBuilder to 
            // their corresponding behaviors, "get" and "set" respectively. 
            property.SetGetMethod( currGetPropMthdBldr );
            property.SetSetMethod( currSetPropMthdBldr );
        }
    }
}
